/**
 * @file maze_boutaoshi.js, an implementation for maze.js to generate a maze by Bou-Taoshi method.
 *
 * @author nobu <nobu@ctmk.amanek.jp>
 */

(function(define) {
define([], function() {
	/**
	* @constructor
	*/
	var Maze_Algorithm = function() {
		// initialize
	};
	
	/**
	 * Generate a maze.
	 *
	 * @return {Maze.Result}
	 * @param {Maze.Config} config
	 * @throw {RangeError} It will throw if any config option is invalid.
	 */
	Maze_Algorithm.generate = function (config) {
		return generate_by_bou_taoshi(config);
	};
	
	/**
	 * Signature of this method.
	 */
	Maze_Algorithm.Signature = function() {
		return "bou-taoshi";
	};

	return Maze_Algorithm;

	/**
	 * @class
	 */
	function Work(config) {
		if (config.need_process) {
			this.process = [];
		}
		this.data = [];
	}
	
	/**
	 * Generate a maze by Bou-Taoshi method.
	 * @return {Maze.Result}
	 * @param {Maze.Config} config
	 */
	function generate_by_bou_taoshi(config) {
		validate(config);
		work = new Work(config);

		work.data = Maze_Algorithm.get_data_filled_with_edges(config.width, config.height, Maze_Algorithm.CellType.PATH, Maze_Algorithm.CellType.WALL);
		if (config.need_process) {
			work.process = Maze_Algorithm.get_process_filling_edges(config.width, config.height, Maze_Algorithm.CellType.WALL);
		}

		put_inner_walls(work, config.width, config.height);
		put_walls(work, config.width, config.height);
		
		result = new Maze_Algorithm.Result(config, Maze_Algorithm.Signature());
		if (config.need_data) {
			result.data = work.data;
		}
		if (config.need_process) {
			result.process = work.process;
			result.celltype_to_fill = Maze_Algorithm.CellType.PATH;
		}
		return result;
	}
	
	/**
	 * @param {Maze.Config} config
	 */
	function validate(config) {
		if ((config.width < 5) || (config.width%2 === 0) || (config.height < 5) || (config.height%2 === 0)) {
			// A limitation in this algorithm.
			throw new RangeError("config.width and config.height must be at least 5 and be a odd number.");
		}
	}
	

	/**
	 * Put inner walls.
	 * @param {Maze_Algorithm.Work} work
	 * @param {number} x
	 * @param {number} y
	 */
	function put_inner_walls(work, width, height) {
		var put_wall = ('process' in work) ? function (work, x, y) { return Maze_Algorithm.put_with_process(work, x, y, Maze_Algorithm.CellType.WALL); } : function (work, x, y) { return Maze_Algorithm.put_without_process(work, x, y, Maze_Algorithm.CellType.WALL); };

		for (var y = 2; y < height-2; y += 2) {
			for (var x = 2; x < width-2; x += 2) {
				put_wall(work, x, y);
			}
		}
	}
	
	/**
	 * Put walls next to the inner walls.
	 * @param {Maze_Algorithm.Work} work
	 * @param {number} x
	 * @param {number} y
	 */
	function put_walls(work, width, height) {
		var put_wall = ('process' in work) ? function (work, x, y) { return Maze_Algorithm.put_with_process(work, x, y, Maze_Algorithm.CellType.WALL); } : function (work, x, y) { return Maze_Algorithm.put_without_process(work, x, y, Maze_Algorithm.CellType.WALL); };

		// At first line, put a wall next to the inner wall.
		(function() {
			y = 2;
			for (var x = 2; x < width-2; x += 2) {
				put_wall_randomly(work, x, y, 4, put_wall);
			}
		})();
		
		// Put a wall next to the inner wall but upper side.
		(function() {
			for (var y = 4; y < height-2; y += 2) {
				for (var x = 2; x < width-2; x += 2) {
					put_wall_randomly(work, x, y, 3, put_wall);
				}
			}
		})();
	}

	/**
	 * Put a wall next to the inner wall.
	 * @param {Maze_Algorithm.Work} work
	 * @param {number} x
	 * @param {number} y
	 * @param {number} pattern - specify how many pattern is able to be chosen to decide a direction.
	 * @param {function} put_wall - way to put wall.
	 */
	function put_wall_randomly(work, x, y, pattern, put_wall) {
		var v, h;
		do {
			var d = Math.floor(Math.random()*pattern);
			//    d:  v  h  pattern  dir
			// 0x00:  0  1   1        >
			// 0x01:  0 -1   2        <
			// 0x10:  1  0   3        v
			// 0x11: -1  0   4        ^
			h = (((d & 0x2) >> 1)-1) & (1 - ((d & 0x1) << 1));
			v = (((~d & 0x2) >> 1)-1) & (1 - ((d & 0x1) << 1));
		} while (work.data[y+v][x+h] == Maze_Algorithm.CellType.WALL);
		put_wall(work, x+h, y+v);
	}


});
})(typeof define !== 'undefined' ?
	// Use define for AMD if available
	define :
	// If no define, look for module to export as a CommonJS module.
	typeof module !== 'undefined' ?
	function(deps, factory) { module.exports = factory(); } :
	// If no define or module, attach to current context.
	typeof Maze !== 'undefined' ?
	function(deps, factory) { this.Maze.BouTaoshi = factory(); } :
	function(deps, factory) { this.Maze_BouTaoshi = factory(); }
);
